/**
 * 
 */
package edu.cse5311.operations;

import java.util.List;

import edu.cse5311.constants.Constants;
import edu.cse5311.entity.DataBuffer;
import edu.cse5311.entity.DataDisk;
import edu.cse5311.entity.InputLine;
import edu.cse5311.entity.Item;
import edu.cse5311.entity.Transaction;
import edu.cse5311.impl.Initialize;
import edu.cse5311.impl.Output;

/**
 * @author Karthhik MG/ Shwetha Narayan
 * 
 * This class handles the transactional operations like begin, end, commit, abort, read, write 
 * 
 */
public class TransactionOperations {

	/**
	 * The begin Operation signifies the starting of the transaction.
	 * 
	 * @param inputLineObj
	 */
	public void beginOperation(InputLine inputLineObj) {

		// STEP 1: To keep track of the transaction add it into the transaction
		// Table as one of the active transactions.
		Transaction transaction = inputLineObj.getTransaction();
		transaction.setStatus(Constants.ACTIVE);

		// Add the transaction from the input into the transaction table.
		Initialize.transactionTable.put(transaction.getTransactionId(),
				transaction);

		// Step 2: Write into the Log buffer the transaction status.
		LogOperations logOperations = new LogOperations();
		logOperations.updateLogBuffer(null, inputLineObj.getOperation(),
				transaction);

		// Step 3: Write into the output file a summary of what is done
		Output outputOperations = new Output();
		String outputLine = Constants.BEGIN_OPERATION_OUTPUT.replaceAll(
				Constants.TRANSACTION_REPLACE,
				String.valueOf(transaction.getTransactionId()));
		outputOperations.outputDetailsToFile(outputLine);
		
		outputLine = Constants.BEGIN_TRANSACTION_TABLE_OUTPUT;
		outputLine = outputLine.replaceAll(Constants.TRANSACTION_REPLACE, String.valueOf(inputLineObj.getTransaction().getTransactionId()));
		outputOperations.outputDetailsToFile(outputLine);
		outputOperations.outputDetailsToFile("\n");
	}

	/**
	 * The method is called when the input file suggest the occurrence of the
	 * end of a transaction
	 * 
	 * @param inputLineObj
	 */
	public void endOpration(InputLine inputLineObj) {

		// STEP 1: To keep track of the transaction add it into the transaction
		// Table as one of the active transactions.
		Transaction transaction = inputLineObj.getTransaction();
		transaction.setStatus(Constants.COMMITTED);
		transaction.setModifiedItems(Initialize.transactionTable.get(
				transaction.getTransactionId()).getModifiedItems());

		// Change the status of the transaction in the transaction table as it
		// is committed.
		Initialize.transactionTable.put(transaction.getTransactionId(),
				transaction);

		// Step 2: Write into the Log buffer the transaction status.
		LogOperations logOperations = new LogOperations();
		logOperations.updateLogBuffer(null, inputLineObj.getOperation(),
				inputLineObj.getTransaction());

		// Step 3: Write into the output file a summary of what is done
		Output outputOperations = new Output();
		String outputLine = Constants.END_OPERATION_OUTPUT.replaceAll(
				Constants.TRANSACTION_REPLACE, String.valueOf(inputLineObj
						.getTransaction().getTransactionId()));
		outputOperations.outputDetailsToFile(outputLine);
		
	}

	/**
	 * this function changes the status of all the operations of the corresponding transaction from 
	 * active state to committed state 
	 * @param inputLineObj
	 */
	public void commitOperation(InputLine inputLineObj) {

		// STEP 1: To keep track of the transaction add it into the transaction
		// Table as one of the active transactions.
		Transaction transaction = inputLineObj.getTransaction();
		transaction.setStatus(Constants.COMMITTED);
		transaction.setModifiedItems(Initialize.transactionTable.get(
				transaction.getTransactionId()).getModifiedItems());

		// Change the status of the transaction in the transaction table as it
		// is committed.
		Initialize.transactionTable.put(transaction.getTransactionId(),
				transaction);

		// Step 2: Write into the Log buffer the transaction status.
		LogOperations logOperations = new LogOperations();
		logOperations.updateLogBuffer(null, inputLineObj.getOperation(),
				transaction);
		
		// STEP 3: Flush the log buffer into the Log Disk
		// This is done so as to not to loose any commit operations done by the
		// transaction in the log buffer, but not yet written to the log disk.
		logOperations.writeToLogDisk();

		// Step 3: Write into the output file a summary of what is done
		Output outputOperations = new Output();
		String outputLine = Constants.COMMIT_OPERATION_OUTPUT.replaceAll(
				Constants.TRANSACTION_REPLACE,
				String.valueOf(transaction.getTransactionId()));
		outputOperations.outputDetailsToFile(outputLine);

		outputLine = Constants.COMMIT_TRANSACTION_TABLE_OUTPUT;
		outputLine = outputLine.replaceAll(Constants.TRANSACTION_REPLACE, String.valueOf(inputLineObj.getTransaction().getTransactionId()));
		outputOperations.outputDetailsToFile(outputLine);
		outputOperations.outputDetailsToFile("\n");
	}

	/**
	 * This function changes the status of all the operations of the corresponding transaction from 
	 * active state to aborted state
	 * @param inputLineObj
	 */
	public void abortOperation(InputLine inputLineObj) {

		// STEP 1: Find the items that are changed and change them to the
		// before image.
		List<Item> modifiedItems = Initialize.transactionTable.get(
				inputLineObj.getTransaction().getTransactionId())
				.getModifiedItems();

		// Step 3: Write into the output file a summary of what is done
		Output outputOperations = new Output();
		String outputLine = Constants.ABORT_OPERATION_OUTPUT.replaceAll(
				Constants.TRANSACTION_REPLACE, String.valueOf(inputLineObj
						.getTransaction().getTransactionId()));
		outputOperations.outputDetailsToFile(outputLine);

		// Iterate through the list of modified items and reset their values to
		// the before image in the data cache
		for (int i = modifiedItems.size() - 1; i >= 0; i--) {
			Item item = modifiedItems.get(i);

			// Get the index of the item in the Data disk
			int index = HelperOperations.getItemIndexOnDisk(item.getItemName());

			// Get the buffer number where the item is stored from the DBAT
			int dataBufferIndex = -1;
			if (Initialize.dataBufferAllocationTable.containsKey(index)) {
				dataBufferIndex = Initialize.dataBufferAllocationTable
						.get(index);
			}

			if (dataBufferIndex != -1) {
				DataBuffer dataBufferItem = Initialize.dataCache
						.get(dataBufferIndex);
				dataBufferItem.setItemValue(item.getItemBFIM());
				dataBufferItem.setDirtyBit(false);
				Initialize.dataCache.set(dataBufferIndex, dataBufferItem);

				// Print the change done to output file
				outputLine = Constants.ABORT_OPERATION_OUTPUT_RESET.replaceAll(
						Constants.ITEM_NAME_REPLACE,
						String.valueOf(item.getItemName()));
				outputLine = outputLine.replaceAll(
						Constants.BEFORE_IMAGE_REPLACE,
						String.valueOf(item.getItemBFIM()));
				outputOperations.outputDetailsToFile(outputLine);

			} else {

				outputLine = "Item " + item.getItemName()
						+ " could not be found in the data buffer.";
				outputOperations.outputDetailsToFile(outputLine);

			}

		}

		// STEP 2: Remove from transaction table the transaction that is
		// aborted.
		Initialize.transactionTable.remove(inputLineObj.getTransaction()
				.getTransactionId());

		outputLine = Constants.END_TRANSACTION_TABLE_OUTPUT;
		outputLine = outputLine.replaceAll(Constants.TRANSACTION_REPLACE, String.valueOf(inputLineObj.getTransaction().getTransactionId()));
		outputOperations.outputDetailsToFile(outputLine);
		outputOperations.outputDetailsToFile("\n");
	}

	/**
	 * This function reads the item requested from the data buffer, if it is not present in the data buffer
	 * it reads from the data disk to the data buffer and then to the variable
	 * @param inputLineObj
	 */
	public void readOperation(InputLine inputLineObj) {
		// If the data cache is full, stop all the operations and report it to
		// the user.
		if (Initialize.dataCache.size() > 20) {
			Output output = new Output();
			output.outputDetailsToFile(Constants.CACHE_FULL);
		}
		if (Initialize.dataCache.size() <= 20) {
			/*
			 * STEP 1: check if the item to be read is present in the data cache
			 * using the DataBufferAllocation table.
			 */
			// first get the index of the item on disk
			String itemName = inputLineObj.getItem().getItemName();
			int index = HelperOperations.getItemIndexOnDisk(itemName);

			int dataCacheIndex = 0;

			// DBAT - disk(KEY) Vs Cache(value)
			// search this index on DBAT
			if (Initialize.dataBufferAllocationTable.containsKey(index)) {
				
				
				dataCacheIndex = Initialize.dataBufferAllocationTable
						.get(index);

				// return the value for the key
				DataBuffer readItem = Initialize.dataCache.get(dataCacheIndex);

				// Check if the item is available in the transaction table as a
				// modified item
				int BFIM = 0;
				int AFIM = readItem.getItemValue();
				if (Initialize.transactionTable.containsKey(inputLineObj
						.getTransaction().getTransactionId())) {

					Transaction transaction = Initialize.transactionTable
							.get(inputLineObj.getTransaction()
									.getTransactionId());

					if (transaction.getModifiedItems() != null) {
						for (int i = transaction.getModifiedItems().size() - 1; i >= 0; i--) {
							if (transaction
									.getModifiedItems()
									.get(i)
									.getItemName()
									.equals(inputLineObj.getItem()
											.getItemName())) {
								BFIM = transaction.getModifiedItems().get(i)
										.getItemBFIM();
								AFIM = transaction.getModifiedItems().get(i)
										.getItemAFIM();
								break;
							}
						}
					}

				}

				Item item = new Item();
				item.setItemName(inputLineObj.getItem().getItemName());
				item.setItemBFIM(BFIM);
				item.setItemAFIM(AFIM);

				String text = Constants.READ_OPERATION_OUTPUT;
				Output output = new Output();
				text = text.replaceAll(Constants.TRANSACTION_REPLACE, String
						.valueOf(inputLineObj.getTransaction()
								.getTransactionId()));
				text = text.replaceAll(Constants.ITEM_NAME_REPLACE,
						inputLineObj.getItem().getItemName());
				output.outputDetailsToFile(text);

				output.outputDetailsToFile(Constants.READ_OPERATION_FROM_CACHE);
				
				// update log buffer
				LogOperations logop = new LogOperations();
				logop.updateLogBuffer(item, inputLineObj.getOperation(),
						inputLineObj.getTransaction());
			} else if (index != -1) {
				Output output = new Output();
				output.outputDetailsToFile(Constants.READ_OPERATION_FROM_DISK);
				
				// read directly from the disk
				DataDisk dataDisk = Initialize.dataDisk.get(index);

				// add the newly read item to cache by setting the dirty bit to
				// false
				DataBuffer newData = new DataBuffer();
				newData.setItemValue(dataDisk.getItemValue());
				newData.setTransactionId(inputLineObj.getTransaction()
						.getTransactionId());
				newData.setDirtyBit(false);

				// Before we add the newly read item to the buffer we check for
				// the free spaces in the buffer.

				// Always the first item in the list is taken;
				dataCacheIndex = Initialize.freeDataBufferList.get(0);
				Initialize.freeDataBufferList.remove(0);

				// Add the newly read item in the corresponding
				Initialize.dataCache.add(dataCacheIndex, newData);

				// update data buffer allocation table
				Initialize.dataBufferAllocationTable.put(index, dataCacheIndex);
				output.outputDetailsToFile(Constants.DATA_BUFFER_ALLOC_TABLE_ENTRY);
				
				Item item = new Item();
				item.setItemName(dataDisk.getItemName());
				item.setItemBFIM(dataDisk.getItemValue());
				item.setItemAFIM(dataDisk.getItemValue());

				String text = Constants.READ_OPERATION_OUTPUT;
				text = text.replaceAll(Constants.TRANSACTION_REPLACE, String
						.valueOf(inputLineObj.getTransaction()
								.getTransactionId()));
				text = text.replaceAll(Constants.ITEM_NAME_REPLACE,
						inputLineObj.getItem().getItemName());
				output.outputDetailsToFile(text);

				// update log buffer
				LogOperations logop = new LogOperations();
				logop.updateLogBuffer(item, inputLineObj.getOperation(),
						inputLineObj.getTransaction());
			} else {
				Output output = new Output();
				String text = Constants.READ_OPERATION_FAIL;
				output.outputDetailsToFile(text);
			}
		}
		Output output = new Output();
		output.outputDetailsToFile("\n");
	}

	/**
	 * This function writes to the data buffer the modified value of an item. If the item is not 
	 * present on the disk then it first reads to the data buffer and then modifies the 
	 * value to the new value 
	 * @param inputLineObj
	 */
	public void writeOperation(InputLine inputLineObj) {

		if (Initialize.dataCache.size() > 20) {
			Output output = new Output();
			output.outputDetailsToFile(Constants.CACHE_FULL);
		}
		if (Initialize.dataCache.size() <= 20) {
			// check if the item to be written is present in the data cache
			// using the DataBufferAllocation table
			// DBAT - disk(KEY) Vs Cache(value)
			// first get the index of the item on data disk
			String itemName = inputLineObj.getItem().getItemName();
			int index = HelperOperations.getItemIndexOnDisk(itemName);

			int dataCacheIndex = 0;
			// search this index on DBAT

			// if the data item to be modified is on the data cache
			if (Initialize.dataBufferAllocationTable.containsKey(index)) {
				dataCacheIndex = Initialize.dataBufferAllocationTable
						.get(index);
				// return the value for the key
				DataBuffer writeItem = Initialize.dataCache.get(dataCacheIndex);
				int BFIM = writeItem.getItemValue();
				// Create the item object with AFIM and BFIM
				Item item = new Item();
				item.setItemName(inputLineObj.getItem().getItemName());
				item.setItemAFIM(inputLineObj.getItem().getItemAFIM());
				item.setItemBFIM(BFIM);

				// Set the new value to the write object
				writeItem.setItemValue(inputLineObj.getItem().getItemAFIM());
				writeItem.setDirtyBit(true);

				Initialize.dataCache.set(dataCacheIndex, writeItem);

				// write to output file
				Output output = new Output();
				String text = Constants.WRITE_OPERATION_OUTPUT;
				text = text.replaceAll(Constants.TRANSACTION_REPLACE, String
						.valueOf(inputLineObj.getTransaction()
								.getTransactionId()));
				text = text.replaceAll(Constants.ITEM_NAME_REPLACE,
						inputLineObj.getItem().getItemName());
				text = text.replaceAll(Constants.ITEM_VALUE_REPLACE,
						String.valueOf(inputLineObj.getItem().getItemAFIM()));
				output.outputDetailsToFile(text);

				// update log buffer
				LogOperations logop = new LogOperations();
				logop.updateLogBuffer(item, inputLineObj.getOperation(),
						inputLineObj.getTransaction());
			}

			// if the data item to be modified is not on the data cache
			else {
				// read directly from the disk
				DataDisk dataDisk = Initialize.dataDisk.get(index - 1);
				dataDisk.setItemValue(inputLineObj.getItem().getItemAFIM());
				// add the newly read item to cache and make changes and also
				// set the dirty bit to true
				DataBuffer newdata = new DataBuffer();
				newdata.setItemValue(dataDisk.getItemValue());
				newdata.setTransactionId(inputLineObj.getTransaction()
						.getTransactionId());
				newdata.setDirtyBit(true);

				// Before we add the newly read item to the buffer we check for
				// the free spaces in the buffer.
				// Always the first item in the list is taken;
				dataCacheIndex = Initialize.freeDataBufferList.get(0);
				Initialize.freeDataBufferList.remove(0);

				Initialize.dataCache.add(dataCacheIndex, newdata);

				// update data buffer allocation table
				Initialize.dataBufferAllocationTable.put(index, dataCacheIndex);

				Item item = new Item();
				item.setItemName(dataDisk.getItemName());
				item.setItemBFIM(inputLineObj.getItem().getItemBFIM());
				item.setItemAFIM(inputLineObj.getItem().getItemAFIM());

				// write to output file
				Output output = new Output();
				String text = Constants.WRITE_OPERATION_OUTPUT;
				text = text.replaceAll(Constants.TRANSACTION_REPLACE, String
						.valueOf(inputLineObj.getTransaction()
								.getTransactionId()));
				text = text.replaceAll(Constants.ITEM_NAME_REPLACE,
						inputLineObj.getItem().getItemName());
				text = text.replaceAll(Constants.ITEM_VALUE_REPLACE,
						String.valueOf(inputLineObj.getItem().getItemAFIM()));
				output.outputDetailsToFile(text);

				// update log buffer
				LogOperations logop = new LogOperations();
				logop.updateLogBuffer(item, inputLineObj.getOperation(),
						inputLineObj.getTransaction());
			}
		}
		Output outputOperations = new Output();
		String outputLine;
		outputLine = Constants.WRITE_TRANSACTION_TABLE_OUTPUT;
		outputLine = outputLine.replaceAll(Constants.TRANSACTION_REPLACE, String.valueOf(inputLineObj.getTransaction().getTransactionId()));
		outputOperations.outputDetailsToFile(outputLine);
		outputOperations.outputDetailsToFile("\n");
	}

}
